//////////////////////////////////////////////////////////////////////////////
//       Copyright (c) 2003 - RealTime Gaming.  All rights reserved.        //
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
// Includes
//////////////////////////////////////////////////////////////////////////////
#include "KeywordSubstitutor.h"
#include "libglob.h"

//////////////////////////////////////////////////////////////////////////////
// Namespaces
//////////////////////////////////////////////////////////////////////////////
using namespace Library;

//****************************************************************************
// KeywordSubstitutor::StringCache Class
//****************************************************************************

//////////////////////////////////////////////////////////////////////////////
/// Default Constructor
//////////////////////////////////////////////////////////////////////////////
KeywordSubstitutor::StringCache::StringCache()
{
	m_pStringCacheTable = new HashTable<String*>();
	m_pKeywordMatchTable = new HashTable<List<String*>*>();
}

//////////////////////////////////////////////////////////////////////////////
/// Destructor
//////////////////////////////////////////////////////////////////////////////
KeywordSubstitutor::StringCache::~StringCache()
{
	// Destroy all strings in the string cache hash table

	// Scope this so the iterator goes out of scope before we destroy the
	//  table.  This is the only place we should really have to do this.
	{
		HashTableIterator<String*> it(m_pStringCacheTable);

		for( it.begin(); it.key(); it++ )
		{
			delete it.value();
		}
	}

	// Destroy the hash table
	delete m_pStringCacheTable;

	// Destroy all strings and all lists in the keyword match table

	// Scope this so the iterator goes out of scope before we destroy the
	//  table.  This is the only place we should really have to do this.
	{
		HashTableIterator<List<String*>*> it(m_pKeywordMatchTable);

		for( it.begin(); it.key(); it++ )
		{
			// Get each list
			List<String*>* pList = it.value();

			if ( pList )
			{
				// Loop trough the list, deleting each string
				ListIterator<String*> it(pList);

				while( it.hasNext() )
				{
					// However, we will cleanup the memory anyway!
					String* pString = it.next();
					delete pString;
				}
			}

			// Destroy the actual list
			delete pList;
		}
	}

	// Destroy the hash table
	delete m_pKeywordMatchTable;
}

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
String*
KeywordSubstitutor::StringCache::FindString( const String& sCacheName )
{
	String* pTemp;
	if ( m_pStringCacheTable->get( sCacheName, &pTemp ) )
	{
		return pTemp;
	}
	else
	{
		return NULL;
	}
}

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
String*
KeywordSubstitutor::StringCache::AddToCache( const String& sCacheName, 
											 const String& string )
{
	// First, if the string is already there, just return it!
	String* pTemp;
	if ( m_pStringCacheTable->get( sCacheName, &pTemp ) )
	{
		return pTemp;
	}
	
	// Otherwise, let's add it to the cache
	pTemp = new String(string);

	if ( m_pStringCacheTable->add( sCacheName, pTemp ) )
	{
		return pTemp;
	}
	else
	{
		delete pTemp;
		return NULL;
	}
}

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
bool8   
KeywordSubstitutor::StringCache::DirtyString( const String& sCacheName )
{
    bool8 bRetVal = false;

	String* pTemp;
	if ( m_pStringCacheTable->get( sCacheName, &pTemp ) )
	{
		pTemp->empty();
        bRetVal = true;
	}

    return bRetVal;
}

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
bool8
KeywordSubstitutor::StringCache::SetKeywordToString( const String& sKeyword, 
													 const String& sCacheName )
{
	bool8 bRetVal = false;
	List<String*>* pList = NULL;

	// First, see if this keyword is already in the table
	if( m_pKeywordMatchTable->get( sKeyword, &pList ) )
	{
		// Now, see if this short name is already in the list
		bool8 bFound = false;		
		ListIterator<String*> it(pList);
		while( it.hasNext() && !bFound )
		{
			String* pString = it.next();
			if ( *pString == sCacheName )
			{
				bFound = true;
			}
		}

		// If we didn't find it, add it to the list
		if ( !bFound )
		{
			String* psCacheName = new String(sCacheName);
			
			if ( psCacheName )
			{
				if ( pList->add( psCacheName ) )
				{
					bRetVal = true;
				}
				else
				{
					delete psCacheName;
				}
			}
		}
	}
	else
	{
		// It's not there, let's add it with a new list
		pList = new List<String*>();

		if ( pList )
		{
			// Add the short name to the list of strings associated with this
			//  keyword
			String* psCacheName = new String(sCacheName);
			
			if ( psCacheName )
			{
				pList->add( psCacheName );
				
				// Add the list to the hashtable, keyed by the keyword
				if ( m_pKeywordMatchTable->add( sKeyword, pList ) )
				{	
					bRetVal = true;
				}
				else
				{
					delete psCacheName;
					delete pList;
				}
			}
			else
			{
				delete pList;
			}
		}
	}
	
	return bRetVal;
}

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
bool8
KeywordSubstitutor::StringCache::KeywordValueChanged( const String& sKeyword )
{
	bool8 bRetVal = false;

	List<String*>* pList = NULL;

	// See if this keyword is already in the table
	if( m_pKeywordMatchTable->get( sKeyword, &pList ) )
	{
		ListIterator<String*> it(pList);

		// Loop through the list of short names associated with this
		//  keyword
		while( it.hasNext() )
		{
			String* pShortName = it.next();

			// Look for this short name in our string table cache, if the
			//  string is there, go ahead and empty it.
			String* pString;
			if ( m_pStringCacheTable->get( *pShortName, &pString ) )
			{
				pString->empty();
			}
		}

		bRetVal = true;
	}

	return bRetVal;
}


//****************************************************************************
// KeywordSubstitutor Class
//****************************************************************************

//////////////////////////////////////////////////////////////////////////////
/// Main Constructor
//////////////////////////////////////////////////////////////////////////////
KeywordSubstitutor::KeywordSubstitutor( LocaleFormatter* pFormatter )
{
	m_pTable = new HashTable<LocaleValue*>();
    
    ASSERT( pFormatter );
    m_pFormatter = pFormatter;
}

//////////////////////////////////////////////////////////////////////////////
/// Destructor
//////////////////////////////////////////////////////////////////////////////
KeywordSubstitutor::~KeywordSubstitutor()
{
	// Destroy all the values in the hash table

	// Scope this so the iterator goes out of scope before we destroy the
	//  table.  This is the only place we should really have to do this.
	{
		HashTableIterator<LocaleValue*> it(m_pTable);

		for( it.begin(); it.key(); it++ )
		{
			delete it.value();
		}
	}

	// Destroy the hash table
	delete m_pTable;
}

//////////////////////////////////////////////////////////////////////////////
/// return@             True if the keyword, value pair was successfully 
///                         added. 
///
/// param@ sKeyword     Keyword string to be replaced with value.
///
/// param@ nCurrency    Integer currency value.
//////////////////////////////////////////////////////////////////////////////
bool8
KeywordSubstitutor::AddCurrencyValue( const String& sKeyword, int32 nCurrency )
{
    bool8 bRetVal = false;

    // Handle the case that we were constructed with an invalid locale
    //  formatter, so we don't crash!
    if ( !m_pFormatter )
    {
        ASSERT(false);
        return bRetVal;
    }

    // First, ask our locale formatter to create a locale value for us.
    CurrencyValue* pValue = (CurrencyValue*) 
		                m_pFormatter->CreateValue( LocaleFormatter::eCurrency );

    // See if that worked
    if ( pValue )
    {
        // Set the value
        pValue->SetValue( nCurrency );
        
        // Add it to our internal hash table
        bRetVal = m_pTable->add( sKeyword, pValue );
    }

    return bRetVal;
}

//////////////////////////////////////////////////////////////////////////////
/// return@             True if the keyword, value pair was successfully 
///                         added. 
///
/// param@ sKeyword     Keyword string to be replaced with value.
///
/// param@ string		String value.
//////////////////////////////////////////////////////////////////////////////
bool8
KeywordSubstitutor::AddStringValue( const String& sKeyword, String string )
{
    bool8 bRetVal = false;

    // Handle the case that we were constructed with an invalid locale
    //  formatter, so we don't crash!
    if ( !m_pFormatter )
    {
        ASSERT(false);
        return bRetVal;
    }

    // First, ask our locale formatter to create a locale value for us.
    StringValue* pValue = (StringValue*) 
		                m_pFormatter->CreateValue( LocaleFormatter::eString );

    // See if that worked
    if ( pValue )
    {
        // Set the value
        pValue->SetValue( string );
        
        // Add it to our internal hash table
        bRetVal = m_pTable->add( sKeyword, pValue );
    }

    return bRetVal;
}

//////////////////////////////////////////////////////////////////////////////
/// return@             True if the keyword, value pair was successfully 
///                        updated. 
///
/// param@ sKeyword     Keyword string to be replaced with value.
///
/// param@ nCurrency    Integer currency value.
//////////////////////////////////////////////////////////////////////////////
bool8
KeywordSubstitutor::UpdateCurrencyValue( const String& sKeyword, int32 nCurrency )
{
    bool8 bRetVal = false;

	LocaleValue* pTemp;
	if ( m_pTable->get( sKeyword, &pTemp ) )
	{
        ((CurrencyValue*)pTemp)->SetValue( nCurrency );
		bRetVal = true;
    }

    // Tell the cache that this keyword value has changed
    m_oCache.KeywordValueChanged( sKeyword );

    return bRetVal;
}

//////////////////////////////////////////////////////////////////////////////
/// return@             True if the keyword, value pair was successfully 
///                        updated. 
///
/// param@ sKeyword     Keyword string to be replaced with value.
///
/// param@ string	    String value.
//////////////////////////////////////////////////////////////////////////////
bool8
KeywordSubstitutor::UpdateStringValue( const String& sKeyword, String string )
{
    bool8 bRetVal = false;

	LocaleValue* pTemp;
	if ( m_pTable->get( sKeyword, &pTemp ) )
	{
        ((StringValue*)pTemp)->SetValue( string );
		bRetVal = true;
    }
    
    // Tell the cache that this keyword value has changed
    m_oCache.KeywordValueChanged( sKeyword );

    return bRetVal;
}

//////////////////////////////////////////////////////////////////////////////
/// return@             True if the keyword, value pair was successfully 
///                        removed.
///
/// param@ sKeyword     Keyword string to be removed.
//////////////////////////////////////////////////////////////////////////////
bool8
KeywordSubstitutor::RemoveValue( const String& sKeyword )
{
	return ( m_pTable->remove( sKeyword ) );
}

//////////////////////////////////////////////////////////////////////////////
/// return@             Const reference to string with keywords substituted
///                      for values. Note, if the string passed in contains
///                      no keywords, the return will simply equal the string
///                      passed in.
///
/// param@ sCacheName   Used a key for internal cahce.
///
/// param@ string       String to search for keywords and replace with values.
///
/// This method takes the string passed in and searches it for all keywords
///   that have been added to the keyword substitutor and replaces each
///   keyword with the current value associated with that keyword.
//////////////////////////////////////////////////////////////////////////////
const String& 
KeywordSubstitutor::PerformSubstitution( const String& sCacheName, const String& string )
{
    bool bAnySubsMade = false;

    // See if this string is in the cache
    String* pCacheString = m_oCache.FindString( sCacheName );

    // If we have a sting in the cache and it's not empty, then it should
    //  be valid, so go ahead and just return it!
    if ( pCacheString && !pCacheString->isEmpty() )
    {
        return *pCacheString;
    }

    // Look for all keywords in the string
	HashTableIterator<LocaleValue*> it(m_pTable);
	for( it.begin(); it.key(); it++ )
	{
		const String* psKeyword  = it.key();
        LocaleValue* pValue = it.value();

        // Is the keyword in this string?
        if ( string.find( *psKeyword ) != String::NPOS )
        {
            // Yes it is, so we need to replace the keyword in the string
            
            // We are going to modify the string, so if it's not in the cache it needs
            // to be.
            if ( !pCacheString )
            {
                pCacheString = m_oCache.AddToCache( sCacheName, string );
                ASSERT( pCacheString );
                if ( !pCacheString )
                {
                    return string;
                }
            }
            
            // If the cache string has been cleared, we need to re-copy it
            if ( pCacheString->isEmpty() )
            {
                *pCacheString = string;
            }

            // Format the value that goes with this keyword
            String sValue;
            pValue->Format( sValue );

            // Now replace it in the string
            pCacheString->replace( *psKeyword, sValue );

            // Tell the cache that this keyword is used in this string
            m_oCache.SetKeywordToString( *psKeyword,  sCacheName );

            // Update flag indicating that we have made substitutions
            bAnySubsMade = true;
        } 
	}

    // If we made no substitutions, go ahead and return the string passed in
    if ( !bAnySubsMade )
    {
	    return string;
    }
    else
    {
        // Otherwise, we return the string that's in our cache
        return *pCacheString;
    }
}

//////////////////////////////////////////////////////////////////////////////
/// return@             True if the string was found in the cache.
///
/// param@ sCacheName   Used a key for internal cahce.
///
/// This method marks the given string as "dirty" in the cache, thus it will
///   be re-built the next time it is asked for.
//////////////////////////////////////////////////////////////////////////////
bool8 
KeywordSubstitutor::DirtyCacheString( const String& sCacheName )
{
    return m_oCache.DirtyString( sCacheName );
}
